﻿using System;
using System.CodeDom.Compiler;
using System.Collections.ObjectModel;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Resources;
using Microsoft.CSharp;
using Microsoft.JScript;
using Microsoft.VisualBasic;

namespace Automator.Runtime.Compiler
{
    /// <summary>
    /// Class for compiling and executing dynamic user code
    /// </summary>
    public class DynamicCompiler
    {
        #region Fields
        
        private static ObservableCollection<string> _sourceLanguages =
            new ObservableCollection<string>() { "C#", "VB.NET", "JScript" };
        private CompilerResults results;
        private static CompilerParameters cparams;
        private SourceType _sourceType;
        
        #endregion
        
        #region Properties
        
        public CompilerErrorCollection Errors
        {
            get { return (results != null) ? results.Errors : null; }
        }
        
        public static ObservableCollection<string> SourceLanguages
        {
            get { return _sourceLanguages; }
        }
        
        #endregion
        
        #region Methods
        
        static DynamicCompiler()
        {
            cparams = new CompilerParameters();
            cparams.ReferencedAssemblies.Add("mscorlib.dll");
            cparams.ReferencedAssemblies.Add("System.dll");
            cparams.ReferencedAssemblies.Add("System.Xml.dll");
            cparams.ReferencedAssemblies.Add("System.Xml.Linq.dll");
            cparams.ReferencedAssemblies.Add("System.Core.dll");
            cparams.ReferencedAssemblies.Add("Microsoft.CSharp.dll");
            cparams.ReferencedAssemblies.Add("System.Data.dll");
            cparams.ReferencedAssemblies.Add("System.Data.DataSetExtensions.dll");
            cparams.GenerateExecutable = false;
            cparams.GenerateInMemory = true;
            cparams.TreatWarningsAsErrors = true;
        }
        
        public DynamicCompiler(string language, string source, SourceType type)
        {
            Uri uri;
            CodeDomProvider provider;
            string templateName = String.Empty;
            
            _sourceType = type;
            switch (_sourceType)
            {
                case SourceType.Script:
                    templateName = "ScriptTemplate";
                    break;
                case SourceType.Condition:
                    templateName = "ConditionTemplate";
                    break;
            }

            switch (language)
            {
                case "C#":
                    uri = new Uri(String.Format("/Automator.Runtime;component/Resources/Templates/{0}.cs", templateName), UriKind.Relative);
                    provider = new CSharpCodeProvider();
                    break;
                case "VB.NET":
                    uri = new Uri(String.Format("/Automator.Runtime;component/Resources/Templates/{0}.vb", templateName), UriKind.Relative);
                    provider = new VBCodeProvider();
                    break;
                case "JScript":
                    uri = new Uri(String.Format("/Automator.Runtime;component/Resources/Templates/{0}.js", templateName), UriKind.Relative);
                    provider = new JScriptCodeProvider();
                    break;
                    default: throw new NullReferenceException();
            }
            
            StreamResourceInfo info = Application.GetResourceStream(uri);
            info.Stream.Position = 0;
            string code;
            using (TextReader reader = new StreamReader(info.Stream))
            {
                code = reader.ReadToEnd().Replace("<<<script>>>", source);
            }
            
            //MessageBox.Show(code);
            results = provider.CompileAssemblyFromSource(cparams, code);
        }
        
        private object ExecuteScript(object value)
        {
            object obj = results.CompiledAssembly.CreateInstance("Automator.Runtime.Compiler.Script");
            Type t = obj.GetType();
            return t.InvokeMember("Main", BindingFlags.InvokeMethod, null, obj, new object[] { value });
        }
        
        private bool ExecuteCondition(object value)
        {
            object obj = results.CompiledAssembly.CreateInstance("Automator.Runtime.Compiler.Condition");
            Type t = obj.GetType();
            return (bool)t.InvokeMember("Main", BindingFlags.InvokeMethod, null, obj, new object[] { value });
        }
        
        public object Execute(object value)
        {
            switch (_sourceType)
            {
                    case SourceType.Script: return ExecuteScript(value);
                    case SourceType.Condition: return ExecuteCondition(value);
                    default: return null;
            }
        }
        
        #endregion
    }
}
